/*
 *    Geotoolkit - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2016, Geomatys
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */

package org.geotoolkit.wps.xml.v200;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlSchemaType;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.datatype.XMLGregorianCalendar;
import org.geotoolkit.atom.xml.Link;
import org.geotoolkit.wps.xml.WPSResponse;
import org.geotoolkit.wps.xml.v100.LegacyStatus;

/**
 * <p>Java class for anonymous complex type.
 *
 * <p>The following schema fragment specifies the expected content contained within this class.
 *
 * <pre>
 * &lt;complexType>
 *   &lt;complexContent>
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       &lt;sequence>
 *         &lt;element ref="{http://www.opengis.net/wps/2.0}JobID" minOccurs="0"/>
 *         &lt;element ref="{http://www.opengis.net/wps/2.0}ExpirationDate" minOccurs="0"/>
 *         &lt;element name="Output" type="{http://www.opengis.net/wps/2.0}DataOutput" maxOccurs="unbounded"/>
 *       &lt;/sequence>
 *     &lt;/restriction>
 *   &lt;/complexContent>
 * &lt;/complexType>
 * </pre>
 *
 *
 */
@XmlType(name = "", propOrder = {
    "jobID",
    "expirationDate",
    "outputV2",
    "process",
    "status",
    "dataInputs",
    "outputDefinitions",
    "processOutputs"
})
@XmlRootElement(name = "Result")
public class Result extends DocumentBase  implements WPSResponse{

    protected String jobID;
    @XmlElement(name = "ExpirationDate")
    @XmlSchemaType(name = "dateTime")
    protected XMLGregorianCalendar expirationDate;
    /**
     * List of output values generated by source process.
     *
     * @implNote JAXB anotation is put on a private getter, as it should be
     * written only when marshalling WPS v2.
     *
     * When writing WPS v1, we transform the content of this list to generate
     * compliant ProcessOutputs form.
     */
    protected List<DataOutput> output;

    @XmlTransient
    private List<Link> links;

    public Result() {

    }

    public Result(List<DataOutput> output, String jobID) {
        this.output = output;
        this.jobID = jobID;
    }

    /**
     *
     * Include if required. A JobId is usually required for
     * a) asynchronous execution
     * b) the Dismiss operation extension, where the client is allowed to actively free server-side resources
     *
     *
     * @return
     *     possible object is
     *     {@link String }
     *
     */
    @XmlElement(name = "JobID")
    public String getJobID() {
        if (FilterByVersion.isV2()) {
            return jobID;
        }
        return null;
    }

    /**
     * Sets the value of the jobID property.
     *
     * @param value
     *     allowed object is
     *     {@link String }
     *
     */
    public void setJobID(String value) {
        this.jobID = value;
    }

    /**
     *
     * Identifier of the Process that was executed.
     * This Process identifier shall be as listed in the ProcessOfferings
     * section of the WPS Capabilities document.
     *
     * @return
     *     possible object is
     *     {@link XMLGregorianCalendar }
     *
     */
    public XMLGregorianCalendar getExpirationDate() {
        return expirationDate;
    }

    /**
     * Sets the value of the expirationDate property.
     *
     * @param value
     *     allowed object is
     *     {@link XMLGregorianCalendar }
     *
     */
    public void setExpirationDate(XMLGregorianCalendar value) {
        this.expirationDate = value;
    }

    /**
     * Gets the value of the output property.
     *
     */
    public List<DataOutput> getOutput() {
        if (output == null) {
            output = new ArrayList<>();
        }
        return this.output;
    }

    public void setOutput(List<DataOutput> outputs) {
        this.output = outputs;
    }


    @XmlElement(name = "Output")
    private List<DataOutput> getOutputV2() {
        if (FilterByVersion.isV2()) {
            return getOutput();
        }

        return null;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[").append(this.getClass().getSimpleName()).append("]\n");
        if (expirationDate != null) {
            sb.append("expirationDate:").append(expirationDate).append('\n');
        }
        if (jobID != null) {
            sb.append("jobID:").append(jobID).append('\n');
        }
        if (output != null) {
            sb.append("output:\n");
            for (DataOutput out : output) {
                sb.append(out).append('\n');
            }
        }
        return sb.toString();
    }

    @Override
    public boolean equals(final Object object) {
        if (object == this) {
            return true;
        }
        if (object instanceof Result) {
            final Result that = (Result) object;
            return Objects.equals(this.expirationDate, that.expirationDate) &&
                   Objects.equals(this.jobID, that.jobID) &&
                   Objects.equals(this.output, that.output);
        }
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 73 * hash + Objects.hashCode(this.jobID);
        hash = 73 * hash + Objects.hashCode(this.expirationDate);
        hash = 73 * hash + Objects.hashCode(this.output);
        return hash;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Following section is boilerplate code for WPS v1 retro-compatibility.
    //
    ////////////////////////////////////////////////////////////////////////////

    private ProcessSummary process;
    private LegacyStatus legacyStatus;
    private DataInputs inputs;

    private String serviceInstance;
    private String statusLocation;

    private OutputDefinitions outputDefinitions;

    public Result(String service, String version, String lang, String serviceInstance, ProcessSummary process, List<DataInput> input, List<OutputDefinition> outputDefinitions, List<DataOutput> output, StatusInfo status, String jobID) {
        super(service, version, lang);
        this.output = output;
        this.jobID = jobID;
        this.serviceInstance = serviceInstance;
        this.process = process;
        this.inputs = new DataInputs(input);
        if (status != null) {
            this.legacyStatus = new LegacyStatus(status);
        }
        if (outputDefinitions != null && !outputDefinitions.isEmpty()) {
            this.outputDefinitions = new OutputDefinitions(outputDefinitions);
        }
    }

    @XmlAttribute
    @XmlSchemaType(name = "anyURI")
    @XmlJavaTypeAdapter(FilterV1.String.class)
    @Deprecated
    public String getServiceInstance() {
        return serviceInstance;
    }

    @Deprecated
    public void setServiceInstance(String serviceInstance) {
        this.serviceInstance = serviceInstance;
    }

    @XmlAttribute
    @XmlSchemaType(name = "anyURI")
    @XmlJavaTypeAdapter(FilterV1.String.class)
    @Deprecated
    public String getStatusLocation() {
        return statusLocation;
    }

    @Deprecated
    public void setStatusLocation(String statusLocation) {
        this.statusLocation = statusLocation;
    }

    @XmlElement(name="Process")
    @XmlJavaTypeAdapter(FilterV1.ProcessSummary.class)
    @Deprecated
    public ProcessSummary getProcess() {
        return process;
    }

    @Deprecated
    public void setProcess(ProcessSummary process) {
        this.process = process;
    }

    @XmlElement(name="Status")
    @XmlJavaTypeAdapter(FilterV1.LegacyStatus.class)
    @Deprecated
    public LegacyStatus getStatus() {
        return legacyStatus;
    }

    @Deprecated
    public void setStatus(LegacyStatus status) {
        legacyStatus = status;
    }

    @XmlElement(name="ProcessOutputs")
    private ProcessOutputs getProcessOutputs() {
        if (FilterByVersion.isV1()) {
            final ProcessOutputs outputs = new ProcessOutputs();
            outputs.parent = this;
            // we don't want to write the mark if there is no outputs
            if (outputs.getOutput().isEmpty()) {
                return null;
            }
            return outputs;
        }

        return null;
    }

    private void setProcessOutputs(ProcessOutputs legacyOutput) {
        // do nothing. The output setting is managed via ProcessOutputs subclass
        legacyOutput.parent = this;
    }

    @XmlElement(name = "DataInputs")
    public DataInputs getDataInputs() {
        if (FilterByVersion.isV1()) {
            // we don't want to write the mark if there is no outputs
            if (inputs != null && (inputs.getInputs() == null || inputs.getInputs().isEmpty())) {
                return null;
            }
            return inputs;
        }
        return null;
    }

    public void setDataInputs(DataInputs legacyInput) {
        this.inputs = legacyInput;
    }

    @XmlElement(name = "OutputDefinitions")
    public OutputDefinitions getOutputDefinitions() {
        if (FilterByVersion.isV1()) {
            return outputDefinitions;
        }
        return null;
    }

    public void setOutputDefinitions(OutputDefinitions outputDefinitions) {
        this.outputDefinitions = outputDefinitions;
    }

    private static class ProcessOutputs extends ParentAware<Result> {

        ProcessOutputs() {
            super(Result.class);
        }

        @XmlElement(name="Output")
        private List<DataOutput> getOutput() {
            return checkParent().getOutput();
        }

        private void setOutput(final List<DataOutput> output) {
            checkParent().getOutput().addAll(output);
        }
    }

    private static class DataInputs {

        private List<DataInput> inputs;

        private DataInputs() {

        }

        private DataInputs(List<DataInput> inputs) {
            this.inputs = inputs;
        }

        @XmlElement(name = "Input")
        private List<DataInput> getInputs() {
            return inputs;
        }

        private void setInputs(final List<DataInput> inputs) {
            this.inputs = inputs;
        }
    }

    private static class OutputDefinitions {

        private List<OutputDefinition> outputs;

        private OutputDefinitions() {

        }

        private OutputDefinitions(List<OutputDefinition> outputs) {
            this.outputs = outputs;
        }

        @XmlElement(name = "Output")
        private List<OutputDefinition> getOutputs() {
            return outputs;
        }

        private void setOutputs(final List<OutputDefinition> outputs) {
            this.outputs = outputs;
        }
    }

    /**
     * @return the links
     */
    public List<Link> getLinks() {
        return links;
    }

    /**
     * @param links the links to set
     */
    public void setLinks(List<Link> links) {
        this.links = links;
    }
}
